// https://cloud.tencent.com/developer/article/1900331
const marching_squares = {
    NONE: 0,
    UP: 1,
    LEFT: 2,
    DOWN: 3,
    RIGHT: 4,
    data: null,
    width: null,
    height: null,
    loop: null,
    nextStep: null,
    upLeft: null,
    upRight: null,
    downLeft: null,
    downRight: null,
    previousStep: null,
    state: null,
};

/**返回图片轮廓的像素点 */
const getBlobOutlinePoints = function (data: Uint8Array, width: number, height: number, loop: boolean) {
    marching_squares.data = data,
        marching_squares.width = width,
        marching_squares.height = height,
        marching_squares.loop = loop;
    var p = getFirstNonTransparentPixelTopDown(),
        result = walkPerimeter(p.x, p.y);
    return marching_squares.width = null,
        marching_squares.height = null,
        marching_squares.data = null,
        marching_squares.loop = null,
        result
}


const getFirstNonTransparentPixelTopDown = function () {
    var t, a, data = marching_squares.data,
        width = marching_squares.width,
        height = marching_squares.height,
        s = 0;
    for (t = 0; t < height; t++) for (a = 0; a < width; a++, s += 4) if (data[s + 3] > 0) return {
        x: a,
        y: t
    };
    return null
}

const walkPerimeter = function (pos_x, pos_y) {
    pos_x < 0 && (pos_x = 0),
        pos_x > marching_squares.width && (pos_x = marching_squares.width),
        pos_y < 0 && (pos_y = 0),
        pos_y > marching_squares.height && (pos_y = marching_squares.height);
    var x = pos_x,
        y = pos_y,
        result = [{ x, y }];
    do {
        switch (step(x, y, marching_squares.data), marching_squares.nextStep) {
            case marching_squares.UP:
                y--;
                break;
            case marching_squares.LEFT:
                x--;
                break;
            case marching_squares.DOWN:
                y++;
                break;
            case marching_squares.RIGHT:
                x++
        }
        x >= 0 && x <= marching_squares.width && y >= 0 && y <= marching_squares.height && result.push({ x, y })
    } while (x !== pos_x || y !== pos_y);
    return marching_squares.loop && result.push({ x, y }),
        result
}

const step = function (x, y, data) {
    var width = marching_squares.width,
        rowOffset = 4 * width,
        upLeftIndex = (y - 1) * rowOffset + 4 * (x - 1),
        isInLeft = x > 0,
        isInRight = x < width,
        isInDown = y < marching_squares.height,
        isInUp = y > 0;
    marching_squares.upLeft = isInUp && isInLeft && data[upLeftIndex + 3] > 0;
    marching_squares.upRight = isInUp && isInRight && data[upLeftIndex + 7] > 0;
    marching_squares.downLeft = isInDown && isInLeft && data[upLeftIndex + rowOffset + 3] > 0;
    marching_squares.downRight = isInDown && isInRight && data[upLeftIndex + rowOffset + 7] > 0;
    marching_squares.previousStep = marching_squares.nextStep;
    marching_squares.state = 0;
    marching_squares.upLeft && (marching_squares.state |= 1);
    marching_squares.upRight && (marching_squares.state |= 2);
    marching_squares.downLeft && (marching_squares.state |= 4);
    marching_squares.downRight && (marching_squares.state |= 8);
    switch (marching_squares.state) {
        case 1:
            marching_squares.nextStep = marching_squares.UP;
            break;
        case 2:
        case 3:
            marching_squares.nextStep = marching_squares.RIGHT;
            break;
        case 4:
            marching_squares.nextStep = marching_squares.LEFT;
            break;
        case 5:
            marching_squares.nextStep = marching_squares.UP;
            break;
        case 6:
            marching_squares.previousStep == marching_squares.UP ? marching_squares.nextStep = marching_squares.LEFT : marching_squares.nextStep = marching_squares.RIGHT;
            break;
        case 7:
            marching_squares.nextStep = marching_squares.RIGHT;
            break;
        case 8:
            marching_squares.nextStep = marching_squares.DOWN;
            break;
        case 9:
            marching_squares.previousStep == marching_squares.RIGHT ? marching_squares.nextStep = marching_squares.UP : marching_squares.nextStep = marching_squares.DOWN;
            break;
        case 10:
        case 11:
            marching_squares.nextStep = marching_squares.DOWN;
            break;
        case 12:
            marching_squares.nextStep = marching_squares.LEFT;
            break;
        case 13:
            marching_squares.nextStep = marching_squares.UP;
            break;
        case 14:
            marching_squares.nextStep = marching_squares.LEFT;
            break;
        default:
            marching_squares.nextStep = marching_squares.NONE
    }
}

const pointLineDistance = function (point, linePointBegin, linePointEnd) {
    let result, k, b;
    if (linePointBegin.x === linePointEnd.x) {
        result = Math.abs(point.x - linePointBegin.x)
    } else {
        // y = kx + b
        k = (linePointEnd.y - linePointBegin.y) / (linePointEnd.x - linePointBegin.x);
        b = linePointBegin.y - k * linePointBegin.x;
        // kx - y + b = 0
        result = Math.abs(k * point.x - point.y + b) / Math.sqrt(Math.pow(k, 2) + 1);
    }
    return result
}

const rdp = function rdp(points, threshold) {
    var point0 = points[0],
        point_end = points[points.length - 1];
    if (points.length < 3) return points;
    for (var slice_index = -1, max_dis = 0, index = 1; index < points.length - 1; index++) {
        var cur_dis = pointLineDistance(points[index], point0, point_end);
        cur_dis > max_dis && (max_dis = cur_dis, slice_index = index)
    }
    if (max_dis > threshold) {
        var left_poins = points.slice(0, slice_index + 1),
            right_points = points.slice(slice_index),
            left_rdp = rdp(left_poins, threshold),
            right_rdp = rdp(right_points, threshold);
        return left_rdp.slice(0, left_rdp.length - 1).concat(right_rdp)
    }
    return [point0, point_end]
};

/**
 * 编辑器里 Regenerate Points  节点动态加载了 spriteFrame ,可以重新获取碰撞组件多边形顶点数组 points方法 采用Marching Squares算法 (由于cocos坐标系和描点的原因可能需要进行一下调整)
 * @param data 纹理图片的像素点数组
 * @param width 纹理的宽度
 * @param height 纹理的高度
 * @param isConvert 是否需要进行坐标转换
 * @param threshold 指明生成贴图轮廓顶点间的最小距离，值越大则生成的点越少
 * @param node 所属节点
 * @param loop 是否循环
 */
const regeneratePoints = function (data: Uint8Array, width: number, height: number, isConvert: boolean = true, threshold: number = 1, node?: cc.Node, loop: boolean = true): cc.Vec2[] {
    let point: cc.Vec2[] = rdp(getBlobOutlinePoints(data, width, height, loop), threshold)
    if (!isConvert) {
        return point
    }

    let spriteFrameRect = node.getComponent(cc.Sprite).spriteFrame.getRect()
    return point.map((item) => {
        item.y = spriteFrameRect.height - item.y
        item.x *= node.width / spriteFrameRect.width
        item.y *= node.height / spriteFrameRect.height
        item.x -= node.anchorX * node.width
        item.y -= node.anchorY * node.height
        return item
    })
}

export { regeneratePoints }